/*
 * SAXAnimationReader.java
 *
 * Created on February 27, 2006, 1:08 AM
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.interaction3d.assembly.sax;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.Scanner;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;


import org.xml.sax.Attributes;


/**
 *
 * @author Michael Nischt
 * @version 0.1
 */
public class SAXAnimationReader
{    
    // <editor-fold defaultstate="collapsed" desc=" Keys ">
    
    private class Keys {
        final float[] values;
        final int[] channels;
        
        Keys(int values, int channels) {
            this.values = new float[values];
            this.channels = new int[channels];
        }
    }
    
    // </editor-fold>      
    
    // <editor-fold defaultstate="collapsed" desc=" Channel ">
    
    private class Channel {
        final String name;
        final int keys;
        final float[] values;
        
        Channel(String name, int keys, int values) {
            this.name = name;
            this.keys = keys;
            this.values = new float[values];
        }
    }
    
    // </editor-fold>    
    
    // <editor-fold defaultstate="collapsed" desc=" AnimationHandler ">
    
    private class AnimationHandler extends DefaultHandler {
        
        private int index, cIndex, ckIndex;
        private Keys    currentKeys;
        private Channel currentChannel;
        
        private Locator locator;
        private AttributeParser ap;
        
        private boolean readKeys = false;
        private boolean readCData = false;
        private StringBuilder cdata = new StringBuilder();
        
        public void characters(char[] ch, int start, int length) throws SAXException {
            if(readCData)
                this.cdata.append(ch, start, length);
        }
        
        public void setDocumentLocator(Locator locator) {
            this.ap = new AttributeParser(locator);
            this.locator = locator;
        }
        
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if(qName.equals("Animation")) {
            } else if(qName.equals("Duration")) {
                this.readCData = true;
                /* Phases */
            } else if(qName.equals("Phases")) {
                phases = new String[this.ap.parseInt(attributes, "count")];
                markers = new float[phases.length+1];
                markers[0] = 0.0f;
                markers[phases.length] = 1.0f;
                this.index = 0;
            } else if(qName.equals("Phase")) {
                final String name = this.ap.parseString(attributes, "name", "");
                phases[this.index++] = name;
            } else if(qName.equals("Marker")) {
                this.readCData = true;
            } else if(qName.equals("KeyFrames")) {
                keys = new Keys[this.ap.parseInt(attributes, "keys")];
                channels = new Channel[this.ap.parseInt(attributes, "channels")];
                this.index = this.cIndex = 0;                
            } else if(qName.equals("Keys")) {
                int values = this.ap.parseInt(attributes, "values");
                int channels = this.ap.parseInt(attributes, "channels");
                this.currentKeys = new Keys(values, channels);
                keys[this.index++] = this.currentKeys;
                this.ckIndex = 0;
                this.readCData = true;
                this.readKeys = true;
            } else if(qName.equals("Channel")) {
                
                if(this.readKeys) {
                    parseFloatArray(this.cdata, this.currentKeys.values);
                    this.readKeys = false;
                }
                cdata.setLength(0);
                this.readCData = true;
                
                final String name = this.ap.parseString(attributes, "name");
                this.currentChannel = new Channel(name, (this.index-1), this.currentKeys.values.length);
                this.currentKeys.channels[this.ckIndex++] = this.cIndex;                
                channels[this.cIndex++] = this.currentChannel;                
            }                        
        }
        
        public void endElement(String uri, String localName, String qName) throws SAXException  {
            this.readCData = false;
            if(qName.equals("Duration")) {
                duration = Double.parseDouble(this.cdata.toString().trim());
            } else if(qName.equals("Phases")) {
                if(index != phases.length)
                    throw new IllegalStateException("Phase tags are less than speficied in he Phases element.");
            } else if(qName.equals("Marker")) {
                markers[this.index] = (float) Double.parseDouble(this.cdata.toString().trim());
            } else if(qName.equals("Channel")) {
                parseFloatArray(this.cdata, this.currentChannel.values);
            }
            cdata.setLength(0);
        }
        
        public void error(SAXParseException e) {
            System.out.println("Animation: error: " + e.getMessage());
        }
        
        public void fatalError(SAXParseException e) {
            System.out.println("Animation: fatalError: "  + e.getMessage());
        }
        
        public void warning(SAXParseException e) {
            System.out.println("Animation: Warning: "  + e.getMessage());
        }
    }
    
    static boolean out = true;
    
    private static void parseFloatArray(CharSequence floats, float[] values) {
        
        int numMatches = 0;
        final Scanner scanner = new Scanner(floats.toString()).useLocale(Locale.ENGLISH);
        while(out && (numMatches < values.length)) {
            values[numMatches++] = scanner.nextFloat();//(float) scanner.nextDouble();//
        }
        if(numMatches < values.length)
            System.out.println("WARNING !!!!");
        // TODO: Exception
    }
    
    private static void parseIntArray(CharSequence ints, int[] values) {
        
        int numMatches = 0;
        final Scanner scanner = new Scanner(ints.toString()).useLocale(Locale.ENGLISH);;
        while(numMatches <values.length) {
            values[numMatches++] = scanner.nextInt();
        }
        if(numMatches < values.length)
            System.out.println("WARNING !!!!");
        // TODO: Exception
    }
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc=" Variables ">
    
    private double duration;
    private float[]   markers  = new float[2];
    private String[]  phases   = new String[1];
    private Keys[]    keys     = new Keys[0];
    private Channel[] channels = new Channel[0];
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc=" Constructors ">
    
    /** Creates a new instance of MeshReader */
    private SAXAnimationReader(SAXParserFactory factory) throws ParserConfigurationException, SAXException {
        //factory.setNamespaceAware(true);
        //factory.setValidating(false);
        //factory.setSchema(SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI).newSchema(getClass().getResource("/net/monoid/games/assembly/xml/validation/Animation.xsd")));
    }
    
    /** Creates a new instance of SAXAnimationReader */
    public SAXAnimationReader(File f) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
        this(SAXParserFactory.newInstance(), f);
    }
    
    /** Creates a new instance of SAXAnimationReader */
    public SAXAnimationReader(String uri) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
        this(SAXParserFactory.newInstance(), uri);
    }
    
    /** Creates a new instance of SAXAnimationReader */
    public SAXAnimationReader(InputStream is) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
        this(SAXParserFactory.newInstance(), is);
    }
    
    /** Creates a new instance of SAXAnimationReader */
    public SAXAnimationReader(InputSource is) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
        this(SAXParserFactory.newInstance(), is);
    }
    
    
    /** Creates a new instance of SAXAnimationReader */
    public SAXAnimationReader(SAXParserFactory factory, File f) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
        this(factory);
        factory.newSAXParser().parse(f, new AnimationHandler());
    }
    
    /** Creates a new instance of SAXAnimationReader */
    public SAXAnimationReader(SAXParserFactory factory, String uri) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
        this(factory);
        factory.newSAXParser().parse(uri, new AnimationHandler());
    }
    
    /** Creates a new instance of SAXAnimationReader */
    public SAXAnimationReader(SAXParserFactory factory, InputStream is) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
        this(factory);
        factory.newSAXParser().parse(is, new AnimationHandler());
    }
    
    /** Creates a new instance of SAXAnimationReader */
    public SAXAnimationReader(SAXParserFactory factory, InputSource is) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
        this(factory);
        factory.newSAXParser().parse(is, new AnimationHandler());
    }
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc=" Animation ">

    public double getDuration() {
        return this.duration;
    }
    
    
    public int getPhaseCount() {
        return this.phases.length;
    }
    public String getPhaseName(int phase) {
        return this.phases[phase];
    }
    public double getPhaseStart(int phase) {
        return this.markers[phase];
    }
    public double getPhaseEnd(int phase) {
        return this.markers[phase+1];
    }
    
    
    public int getKeysCount() {
        return this.keys.length;
    }
    
    public int getChannelCountForKeys(int keys) {
        return this.keys[keys].channels.length;
    }
    public int getChannelForKeys(int keys, int index) {
        return this.keys[keys].channels[index];
    }
    
    
    public int getKeyCount(int keys) {
        return this.keys[keys].values.length;
    }
        
    public float[] getKeyValues(int keys, float[] values) {
        final float[] keyValues = this.keys[keys].values;
        System.arraycopy(keyValues, 0, values, 0, keyValues.length);
        return values;
    }
    public double[] getKeyValues(int keys, double[] values) {
        final float[] keyValues = this.keys[keys].values;
        for(int i=0; i<keyValues.length; i++)
            values[i] = keyValues[i];
        return values;
    }
    
    public float[] getKeyValues(int keys, float[] values, int srcStart, int dstStart, int count) {
        final float[] keyValues = this.keys[keys].values;
        System.arraycopy(keyValues, srcStart, values, dstStart, count);
        return values;
    }
    public double[] getKeyValues(int keys, double[] values, int srcStart, int dstStart, int count) {
        final float[] keyValues = this.keys[keys].values;
        for(int i=0; i<count; i++)
            values[dstStart+i] = keyValues[srcStart+i];
        return values;              
    } 
    
    
    
    public int getChannelCount() {
        return this.channels.length;
    }
    public String getChannelName(int channel) {
        return this.channels[channel].name;
    }
    
    public int getChannelKeys(int channel) {
        return this.channels[channel].keys;
    }    
    
    public float[] getChannelValues(int channel, float[] values) {
        final float[] channelValues = this.channels[channel].values;
        System.arraycopy(channelValues, 0, values, 0, channelValues.length);
        return values;        
    }
    public double[] getChannelValues(int channel, double[] values) {
        final float[] channelValues = this.channels[channel].values;
        for(int i=0; i<channelValues.length; i++)
            values[i] = channelValues[i];
        return values;
    }
    
    public float[] getChannelValues(int channel, float[] values, int srcStart, int dstStart, int count) {
        final float[] channelValues = this.channels[channel].values;
        System.arraycopy(channelValues, srcStart, values, dstStart, channelValues.length);
        return values; 
    }
    public double[] getChannelValues(int channel, double[] values, int srcStart, int dstStart, int count) {
        final float[] channelValues = this.channels[channel].values;
        for(int i=0; i<count; i++)
            values[dstStart+i] = channelValues[srcStart+i];
        return values;
    }
    
    // </editor-fold>    
    
}
